/*
 * Copyright (C) 2005 - 2013 MaNGOS <http://www.getmangos.com/>
 *
 * Copyright (C) 2008 - 2013 Trinity <http://www.trinitycore.org/>
 *
 * Copyright (C) 2010 - 2013 ProjectSkyfire <http://www.projectskyfire.org/>
 *
 * Copyright (C) 2011 - 2013 ArkCORE <http://www.arkania.net/>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#ifndef __ARKCORE_ACHIEVEMENTMGR_H
#define __ARKCORE_ACHIEVEMENTMGR_H

#include <map>
#include <string>

#include "Common.h"
#include <ace/Singleton.h>
#include "DatabaseEnv.h"
#include "DBCEnums.h"
#include "DBCStores.h"

typedef std::list<AchievementCriteriaEntry const*> AchievementCriteriaEntryList;
typedef std::list<AchievementEntry const*> AchievementEntryList;

typedef std::map<uint32, AchievementCriteriaEntryList> AchievementCriteriaListByAchievement;
typedef std::map<uint32, AchievementEntryList> AchievementListByReferencedId;

struct CriteriaProgress
{
    uint32 counter;
    time_t date;          // latest update time.
    bool changed;
};

enum AchievementCriteriaDataType
{
    //value1          value2        comment
    ACHIEVEMENT_CRITERIA_DATA_TYPE_NONE = 0,          // 0              0
    ACHIEVEMENT_CRITERIA_DATA_TYPE_T_CREATURE = 1,          // creature_id    0
    ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE = 2,          // class_id       race_id
    ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_LESS_HEALTH = 3,          // health_percent 0
    ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_DEAD = 4,          // own_team       0             not corpse (not released body), own_team == false if enemy team expected
    ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AURA = 5,          // spell_id       effect_idx
    ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AREA = 6,          // area id        0
    ACHIEVEMENT_CRITERIA_DATA_TYPE_T_AURA = 7,          // spell_id       effect_idx
    ACHIEVEMENT_CRITERIA_DATA_TYPE_VALUE = 8,          // minvalue                     value provided with achievement update must be not less that limit
    ACHIEVEMENT_CRITERIA_DATA_TYPE_T_LEVEL = 9,          // minlevel                     minlevel of target
    ACHIEVEMENT_CRITERIA_DATA_TYPE_T_GENDER = 10,          // gender                       0 = male; 1 = female
    ACHIEVEMENT_CRITERIA_DATA_TYPE_SCRIPT = 11,          // scripted requirement
    ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_DIFFICULTY = 12,          // difficulty                   normal/heroic difficulty for current event map
    ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_PLAYER_COUNT = 13,          // count                        "with less than %u people in the zone"
    ACHIEVEMENT_CRITERIA_DATA_TYPE_T_TEAM = 14,          // team                         HORDE(67), ALLIANCE(469)
    ACHIEVEMENT_CRITERIA_DATA_TYPE_S_DRUNK = 15,          // drunken_state  0             (enum DrunkenState) of player
    ACHIEVEMENT_CRITERIA_DATA_TYPE_HOLIDAY = 16,          // holiday_id     0             event in holiday time
    ACHIEVEMENT_CRITERIA_DATA_TYPE_BG_LOSS_TEAM_SCORE = 17,          // min_score      max_score     player's team win bg and opposition team have team score in range
    ACHIEVEMENT_CRITERIA_DATA_INSTANCE_SCRIPT = 18,          // 0              0             maker instance script call for check current criteria requirements fit
    ACHIEVEMENT_CRITERIA_DATA_TYPE_S_EQUIPED_ITEM = 19,          // item_level     item_quality  for equipped item in slot to check item level and quality
    ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_ID = 20,          // map_id         0             player must be on map with id in map_id
};

#define MAX_ACHIEVEMENT_CRITERIA_DATA_TYPE  21                // maximum value in AchievementCriteriaDataType enum
class Player;
class Unit;

struct AchievementCriteriaData
{
    AchievementCriteriaDataType dataType;
    union
    {
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_NONE              = 0 (no data)
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_T_CREATURE        = 1
        struct
        {
            uint32 id;
        } creature;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_CLASS_RACE = 2
        struct
        {
            uint32 class_id;
            uint32 race_id;
        } classRace;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_LESS_HEALTH = 3
        struct
        {
            uint32 percent;
        } health;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_T_PLAYER_DEAD     = 4
        struct
        {
            uint32 own_team_flag;
        } player_dead;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AURA            = 5
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_T_AURA            = 7
        struct
        {
            uint32 spell_id;
            uint32 effect_idx;
        } aura;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_S_AREA            = 6
        struct
        {
            uint32 id;
        } area;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_VALUE             = 8
        struct
        {
            uint32 minvalue;
        } value;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_T_LEVEL           = 9
        struct
        {
            uint32 minlevel;
        } level;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_T_GENDER          = 10
        struct
        {
            uint32 gender;
        } gender;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_SCRIPT            = 11 (no data)
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_DIFFICULTY    = 12
        struct
        {
            uint32 difficulty;
        } difficulty;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_PLAYER_COUNT  = 13
        struct
        {
            uint32 maxcount;
        } map_players;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_T_TEAM            = 14
        struct
        {
            uint32 team;
        } team;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_S_DRUNK           = 15
        struct
        {
            uint32 state;
        } drunk;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_HOLIDAY           = 16
        struct
        {
            uint32 id;
        } holiday;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_BG_LOSS_TEAM_SCORE= 17
        struct
        {
            uint32 min_score;
            uint32 max_score;
        } bg_loss_team_score;
        // ACHIEVEMENT_CRITERIA_DATA_INSTANCE_SCRIPT        = 18 (no data)
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_S_EQUIPED_ITEM    = 19
        struct
        {
            uint32 item_level;
            uint32 item_quality;
        } equipped_item;
        // ACHIEVEMENT_CRITERIA_DATA_TYPE_MAP_ID            = 20
        struct
        {
            uint32 mapId;
        } map_id;
        // ...
        struct
        {
            uint32 value1;
            uint32 value2;
        } raw;
    };
    uint32 ScriptId;

    AchievementCriteriaData() :
            dataType(ACHIEVEMENT_CRITERIA_DATA_TYPE_NONE)
    {
        raw.value1 = 0;
        raw.value2 = 0;
        ScriptId = 0;
    }

    AchievementCriteriaData(uint32 _dataType, uint32 _value1, uint32 _value2, uint32 _scriptId) :
            dataType(AchievementCriteriaDataType(_dataType))
    {
        raw.value1 = _value1;
        raw.value2 = _value2;
        ScriptId = _scriptId;
    }

    bool IsValid(AchievementCriteriaEntry const* criteria);
    bool Meets(uint32 criteria_id, Player const* source, Unit const* target, uint32 miscvalue1 = 0) const;
};

struct AchievementCriteriaDataSet
{
    AchievementCriteriaDataSet() :
            criteria_id(0)
    {
        // ? by Atomic.
    }
    typedef std::vector<AchievementCriteriaData> Storage;
    void Add(AchievementCriteriaData const& data)
    {
        storage.push_back(data);
    }
    bool Meets(Player const* source, Unit const* target, uint32 miscvalue = 0) const;
    void SetCriteriaId(uint32 id)
    {
        criteria_id = id;
    }

private:
    uint32 criteria_id;
    Storage storage;
};

typedef std::map<uint32, AchievementCriteriaDataSet> AchievementCriteriaDataMap;

struct AchievementReward
{
    uint32 titleId[2];
    uint32 itemId;
    uint32 sender;
    std::string subject;
    std::string text;
};

typedef std::map<uint32, AchievementReward> AchievementRewards;

struct AchievementRewardLocale
{
    StringVector subject;
    StringVector text;
};

typedef std::map<uint32, AchievementRewardLocale> AchievementRewardLocales;

struct CompletedAchievementData
{
    time_t date;
    bool changed;
};

typedef UNORDERED_MAP<uint32, CriteriaProgress> CriteriaProgressMap;
typedef UNORDERED_MAP<uint32, CompletedAchievementData> CompletedAchievementMap;

class Unit;
class Player;
class WorldPacket;

class AchievementMgr
{
public:
    AchievementMgr(Player* pl);
    ~AchievementMgr();

    void Reset();
    static void DeleteFromDB(uint32 lowguid);

    void LoadFromDB(PreparedQueryResult achievementResult, PreparedQueryResult criteriaResult);
    void SaveToDB(SQLTransaction& trans);
    void ResetAchievementCriteria(AchievementCriteriaTypes type, uint64 miscvalue1 = 0, uint64 miscvalue2 = 0, bool evenIfCriteriaComplete = false);
    void UpdateAchievementCriteria(AchievementCriteriaTypes type, uint64 miscvalue1 = 0, uint64 miscvalue2 = 0, Unit *unit = NULL, uint32 time = 0);
    void CompletedAchievement(AchievementEntry const* entry, bool ignoreGMAllowAchievementConfig = false);
    void CheckAllAchievementCriteria();
    void SendAllAchievementData();
    void SendRespondInspectAchievements(Player* player);

    bool HasAchieved(AchievementEntry const* achievement) const;

    Player* GetPlayer()
    {
        return m_player;
    }

    void UpdateTimedAchievements(uint32 timeDiff);
    void StartTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry, uint32 timeLost = 0);
    void RemoveTimedAchievement(AchievementCriteriaTimedTypes type, uint32 entry);          // used for quest and scripted timed achievements

    uint32 GetAchievementPoints()
    {
        return achievementPoints;
    }

private:
    enum ProgressType
    {
        PROGRESS_SET, PROGRESS_ACCUMULATE, PROGRESS_HIGHEST
    };
    void SendAchievementEarned(AchievementEntry const* achievement);
    void SendCriteriaUpdate(AchievementCriteriaEntry const* entry, CriteriaProgress const* progress, uint32 timeElapsed, bool timedCompleted);

    CriteriaProgress* GetCriteriaProgress(AchievementCriteriaEntry const* entry);

    void SetCriteriaProgress(AchievementCriteriaEntry const* entry, uint32 changeValue, ProgressType ptype = PROGRESS_SET);
    void RemoveCriteriaProgress(AchievementCriteriaEntry const* entry);
    void CompletedCriteriaFor(AchievementEntry const* achievement);

    bool IsCompletedCriteria(AchievementCriteriaEntry const* criteria, AchievementEntry const* achievement);
    bool IsCompletedAchievement(AchievementEntry const* entry);

    //void CompleteAchievementsWithRefs(AchievementEntry const* entry);
    void Compress(WorldPacket& data, uint32 newOpcode);
    void Compress(void* dst, uint32 *dst_size, void* src, int src_size);

    Player* m_player;
    CriteriaProgressMap m_criteriaProgress;
    CompletedAchievementMap m_completedAchievements;
    uint32 achievementPoints;
    typedef std::map<uint32, uint32> TimedAchievementMap;
    TimedAchievementMap m_timedAchievements;          // Criteria id/time left in MS
};

class AchievementGlobalMgr
{
    friend class ACE_Singleton<AchievementGlobalMgr, ACE_Null_Mutex> ;
    AchievementGlobalMgr()
    {
        //? by Atomic.
    }
    ~AchievementGlobalMgr()
    {
        //? by Atomic.
    }

public:
    AchievementCriteriaEntryList const& GetAchievementCriteriaByType(AchievementCriteriaTypes type);
    AchievementCriteriaEntryList const& GetTimedAchievementCriteriaByType(AchievementCriteriaTimedTypes type);
    AchievementCriteriaEntryList const* GetAchievementCriteriaByAchievement(uint32 id)
    {
        AchievementCriteriaListByAchievement::const_iterator itr = m_AchievementCriteriaListByAchievement.find(id);
        return itr != m_AchievementCriteriaListByAchievement.end() ? &itr->second : NULL;
    }

    AchievementEntryList const* GetAchievementByReferencedId(uint32 id) const
    {
        AchievementListByReferencedId::const_iterator itr = m_AchievementListByReferencedId.find(id);
        return itr != m_AchievementListByReferencedId.end() ? &itr->second : NULL;
    }

    AchievementReward const* GetAchievementReward(AchievementEntry const* achievement) const
    {
        AchievementRewards::const_iterator iter = m_achievementRewards.find(achievement->ID);
        return iter != m_achievementRewards.end() ? &iter->second : NULL;
    }

    AchievementRewardLocale const* GetAchievementRewardLocale(AchievementEntry const* achievement) const
    {
        AchievementRewardLocales::const_iterator iter = m_achievementRewardLocales.find(achievement->ID);
        return iter != m_achievementRewardLocales.end() ? &iter->second : NULL;
    }

    AchievementCriteriaDataSet const* GetCriteriaDataSet(AchievementCriteriaEntry const *achievementCriteria)
    {
        AchievementCriteriaDataMap::const_iterator iter = m_criteriaDataMap.find(achievementCriteria->ID);
        return iter != m_criteriaDataMap.end() ? &iter->second : NULL;
    }

    bool IsRealmCompleted(AchievementEntry const* achievement) const
    {
        return m_allCompletedAchievements.find(achievement->ID) != m_allCompletedAchievements.end();
    }

    void SetRealmCompleted(AchievementEntry const* achievement)
    {
        m_allCompletedAchievements.insert(achievement->ID);
    }

    void LoadAchievementCriteriaList();
    void LoadAchievementCriteriaData();
    void LoadAchievementReferenceList();
    void LoadCompletedAchievements();
    void LoadRewards();
    void LoadRewardLocales();

private:
    AchievementCriteriaDataMap m_criteriaDataMap;

    // store achievement criterias by type to speed up lookup
    AchievementCriteriaEntryList m_AchievementCriteriasByType[ACHIEVEMENT_CRITERIA_TYPE_TOTAL];
    AchievementCriteriaEntryList m_AchievementCriteriasByTimedType[ACHIEVEMENT_TIMED_TYPE_MAX];
    // store achievement criterias by achievement to speed up lookup
    AchievementCriteriaListByAchievement m_AchievementCriteriaListByAchievement;
    // store achievements by referenced achievement id to speed up lookup
    AchievementListByReferencedId m_AchievementListByReferencedId;

    typedef std::set<uint32> AllCompletedAchievements;
    AllCompletedAchievements m_allCompletedAchievements;

    AchievementRewards m_achievementRewards;
    AchievementRewardLocales m_achievementRewardLocales;
};

#define sAchievementMgr ACE_Singleton<AchievementGlobalMgr, ACE_Null_Mutex>::instance()
#endif
